package com.example.bigwork.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Arrays;

@Aspect
@Component
@Slf4j
public class LoggingAspect {

    /**
     * Pointcut for all methods in the controller package.
     */
    @Pointcut("within(com.example.bigwork.controller..*)")
    public void controllerMethods() {}

    /**
     * Pointcut for all public methods in the service package.
     */
    @Pointcut("execution(public * com.example.bigwork.service..*.*(..))")
    public void serviceMethods() {}


    @Before("controllerMethods()")
    public void logControllerMethodEntry(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            log.info("API Request: {} {} from IP {} | Controller: {} | Method: {} | Args: {}",
                    request.getMethod(),
                    request.getRequestURI(),
                    request.getRemoteAddr(),
                    joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName(),
                    Arrays.toString(joinPoint.getArgs()));
        } else {
            log.info("Entering Controller: {} | Method: {} | Args: {}",
                    joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName(),
                    Arrays.toString(joinPoint.getArgs()));
        }
    }

    @AfterReturning(pointcut = "controllerMethods()", returning = "result")
    public void logControllerMethodExit(JoinPoint joinPoint, Object result) {
        log.info("Exiting Controller: {} | Method: {} | Result: {}",
                joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(),
                result != null ? result.toString().substring(0, Math.min(result.toString().length(), 200)) : "null"); // Log snippet of result
    }


    @Around("serviceMethods()")
    public Object logServiceMethodExecutionTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        long endTime = System.currentTimeMillis();
        log.debug("Service Method: {} | Execution Time: {} ms",
                proceedingJoinPoint.getSignature().toShortString(),
                (endTime - startTime));
        return result;
    }

    @AfterThrowing(pointcut = "controllerMethods() || serviceMethods()", throwing = "exception")
    public void logException(JoinPoint joinPoint, Throwable exception) {
        log.error("Exception in: {}.{}() with cause = '{}' and exception = '{}'",
                joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(),
                exception.getCause() != null ? exception.getCause() : "NULL",
                exception.getMessage(),
                exception); // Log the full stack trace for the exception
    }
}

